package objectClasses;


import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * Singleton class to gather entries from anywhere else in the program
 * It currently holds a map to all the data regarding entries
 * and will fetch queries based on Strings you hand the methods
 * 
 * @author Pegleg
 *
 */
public class Database implements Serializable{
	/**
	 * Used as the code to serialize save file with
	 */
	private static final long serialVersionUID = -5450907284067904760L;
	/**
	 * This is the reference pointer to the database
	 */
	private static Database instance = null; 
	/**
	 * Map with key values set as users->Entry linked list
	 */
	private static TreeMap<String, LinkedList<Entry>> map = new TreeMap<String, LinkedList<Entry>>();
	/**
	 * Constructor always load the file if one exists
	 */
	private static String[] Categories ={"Basket","Scarf","Jacket","Other"};
	public Database() { 
		loadFile();
	} 
	
	public static String[] getCategoryStrings(){
		return Categories;
	}
	/**
	 * Use this to create a new reference to this database I.E
	 * Database test = Database.getInstace();
	 * @return A reference to the address of this class
	 */
	public static Database getInstance() {
		if(instance == null) { 
			instance = new Database(); }
		return instance; 
	}
	/**
	 * This will return null when the map is empty
	 * otherwise it simple gets the entries of a User
	 * based on your string.
	 * @param User
	 * @return a List<Entry> object of user entries
	 */

	public static LinkedList<Entry> getUserEntries(String User){
		if(map.isEmpty()){
			System.out.print("No list to grab! Map is empty\n");
			return null;
		}
		return map.get(User);
	}

	/**
	 * This method adds an entry however only accepts an entry object
	 * 
	 * @param e Database.addEntry(new Entry(your variables));
	 * @throws Exception 
	 */
	public static void addEntry(Entry e) {
		if(map.containsKey(e.getUsername())){
			if(map.get(e.getUsername()).size()>=3){
				System.out.println("Only 3 entries allowed."+ " User:"+ e.getUsername());
				return;
			}				
			for(Entry User_Entries: map.get(e.getUsername())){
				if(User_Entries.getCategory().equals(e.getCategory())){
				throw new IllegalArgumentException();
				
				}
			}
			map.get(e.getUsername()).add(new Entry(e.getUsername(),e.getEntryMaterialsString(),
					e.getCategory(),  e.getIdNumber(), e.getMyDraft()));

		}
		else if(map.isEmpty()){
			map.put(e.getUsername(), new LinkedList<Entry>());
			for(Entry User_Entries: map.get(e.getUsername())){
				if(User_Entries.getCategory().equals(e.getCategory())){
					throw new IllegalArgumentException();
				}		
			}
			map.get(e.getUsername()).add(
					new Entry(e.getUsername(), e.getEntryMaterialsString(),
							e.getCategory(), e.getIdNumber(), e.getMyDraft()));
		}
		else{
			map.put(e.getUsername(), new LinkedList<Entry>());
			map.get(e.getUsername()).add(new Entry(e.getUsername(), e.getEntryMaterialsString(),
					e.getCategory(), e.getIdNumber(), e.getMyDraft()));
		}
	}

	/**
	 * Returns a list containing all entries under a specific
	 * category
	 * CASE-SENSITIVE!
	 * @param Category
	 * @return List<Entry> for the string Category
	 */

	public static LinkedList<Entry> getEntriesBasedOnCat(String Category){
		LinkedList<Entry> temp = new LinkedList<Entry>();
		for (Map.Entry<String, LinkedList<Entry>> e : map.entrySet()){
			for ( Entry user:e.getValue()){
				if(user.getCategory().equals(Category)){
					temp.add(user);
				}
			}
		}
		return temp;

	}

	/**
	 * For testing purposes to make sure entries are valid
	 */
	public static void printAllEntries(){

		for (Map.Entry<String, LinkedList<Entry>> e : map.entrySet()){
			System.out.println(e.getKey()+"'s Entries:");
			for ( Entry user:e.getValue()){
				System.out.println("Their Materials: "+ user.getEntryMaterialsString()
						+ " Category: " + user.getCategory() + " ID_NUM: "+ user.getIdNumber());
			}
		}
	}

	/**
	 * Deletes all of the UserEntries
	 * @param user the case-sensitive username you wisht o delete
	 */

	public static void DeleteAllUserEntries(String user){
		if(!map.containsKey(user)){
			throw new NullPointerException();
		}
		while(!map.get(user).isEmpty()){
			map.get(user).remove();
		}
	}

	/**
	 * Deletes the last recieved entry of the user
	 * @param username their name
	 */
	public static void deleteLastOccurenceEntry(String username){
		if(!map.containsKey(username)){
			System.out.print("No entries left in list");
		}
		else{
			map.get(username).removeLast();
		}
	}
	/**
	 *  Remove at a specific index
	 * @param User Username
	 * @param the_index 0-2
	 */
	public static void removeBasedOnIndex(String User,int the_index){
		if(!map.containsKey(User)||the_index<0||map.get(User).isEmpty()){
			throw new IllegalArgumentException("User not found or trying to remove from empty list");
		}
		else{
		map.get(User).remove(the_index);
		}
	}

	/**
	 * Serializes the entire map
	 * @param filename "Entries.ser"
	 */

	public static void saveFile(){
		try {
			FileOutputStream fos = new FileOutputStream("Entries.ser");
			ObjectOutputStream out = new ObjectOutputStream(fos);
			out.writeObject(map);
			out.flush();
			out.close();
		}
		catch (IOException e) {
			System.out.println(e); 
		}
	}


	/**
	 * reads the serialized file back into the map
	 * @param filename "Entries.ser"
	 */
	@SuppressWarnings("unchecked")
	public static void loadFile(){
		try{
			//use buffering
			InputStream file = new FileInputStream( "Entries.ser" );
			InputStream buffer = new BufferedInputStream( file );
			ObjectInput input = new ObjectInputStream ( buffer );
			try{
				//deserialize the List
				map = (TreeMap<String, LinkedList<Entry>>) input.readObject();
				//display its data
			}
			finally{
				input.close();
			}
		}
		catch(ClassNotFoundException ex){
			System.out.print("No such class"+ex.getMessage());
		}
		catch(IOException ex){
			System.out.print("Cannot read file as it did not exist" +
					" 'Entries.ser' please reload the program");
			saveFile();
		}
	}
	
	public boolean isUserListEmpty(String User){
		if(!map.containsKey(User)){
			throw new IllegalArgumentException("User does not exist in map");
		}
		else{
			return map.get(User).isEmpty();
		}
	}



}